package models;

import play.Logger;
import play.mvc.*;
import play.libs.*;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
import akka.actor.*;
import static akka.pattern.Patterns.ask;

import com.fasterxml.jackson.databind.JsonNode;

import java.util.*;

import models.msgs.*;
import static java.util.concurrent.TimeUnit.*;

public class Table extends UntypedActor {
    
    // Default table.
    static ActorRef defaultTable = Akka.system().actorOf(Props.create(Table.class, 0, 2, 1000, 20));
    
    private List<Player> players = new LinkedList<Player>();
    private Deck deck;
   	private int activeP;
   	private boolean allIn;
   	private int tableMoney;
   	private int entranceBet;
   	private int startingCoins;
   	private int playersNo;
   	
   	public boolean AllowJoining;
       
    public static void join(final String name, WebSocket.In<JsonNode> in, WebSocket.Out<JsonNode> out ) throws Exception{
    	ActorRef human =  Akka.system().actorOf(
                Props.create(HumanStrategy.class, in, out,  defaultTable ));
    	
        String result = (String)Await.result(ask(defaultTable, new JoinPlayerToTable(human, name), 60), Duration.create(1, SECONDS));
                
        if(!"OK".equals(result)) {
        }
    }
    
    @Override
	public void onReceive(Object message) throws Exception {
	     if(message instanceof JoinPlayerToTable)
	     {
	     	joinPlayerToTable((JoinPlayerToTable) message);
	     }
	     else if(message instanceof QuitMessage)
	     {
	    	 for(Player p: players)
	    	 {
	    		 if(p.getActorRef().equals(getSender()))removePlayer(p);
	    	 }
	     }
	     else
	     { 
	    	 unhandled(message);
	     }
	}
	
	public Table(int botsNo, int _playersNo, int _startingCoins, int _entranceBet)
	{
		entranceBet = _entranceBet;
		startingCoins = _startingCoins;
		playersNo = _playersNo;
		deck = new Deck();
		deck.shuffle();
		activeP = botsNo;
		
		AllowJoining = true;
	}
	
	/*
	 * Archives player
	 */
	private void removePlayer(Player player){
		if(player.getStatus() == Player.Status.Active)activeP--;
		player.setStatus(Player.Status.Archived);
	}
	
	private void joinPlayerToTable(JoinPlayerToTable message) throws Exception
	{
		if(AllowJoining && activeP < playersNo)
		{	
			message.actor.tell("OK", getSelf());
			
			Player p = new Player(message.name, message.actor, getSelf(), deck, startingCoins);
			players.add(p);
			
			p.setStatus(Player.Status.Active);			
			activeP++;
			
			if(activeP == playersNo)
			{
				playGame();
			}
		}
		else
		{
			message.actor.tell("It's not allowed to join", getSelf());
		}
	}
	
	private void informAllPlayers(Object message, ActorRef sender)
	{
		String name = "Somebody";
		if(sender.equals(getSelf()))name = "Table";
		else
		{
			for(Player p: players)
	    	{
	    		 if(p.getActorRef().equals(sender))name = p.getName();
	    	}
		}
		
		Logger.info(message.toString());
		for(Player r: players)
		{
			r.getActorRef().tell(new StringMessage(name + ": " + message), getSelf());
		}
	}

	private void betting() throws Exception{
		
		for(Player p: players)
		{
			p.prepareForNewBetting();
		}
		
		Iterator<Player> iterP = players.iterator();
		
		Player lastPlayerRaising = null;
		Player p = iterP.next();
		
		int bet = 0;
		
		informAllPlayers(new InformActualBet(bet), getSelf());
		
		while(lastPlayerRaising != p)
		{
			if(p.getStatus() != Player.Status.Active){
				continue;
			}
			
			int playersBet = p.retrieveBet();
			
			
			//all-in
			if(playersBet == p.getCoins()){
				if(!allIn)
				{
					lastPlayerRaising = p;
				}
				allIn = true;
				informAllPlayers(new StringMessage("all in"), p.getActorRef());
			}
			//check:
			else if(bet == 0 && playersBet == 0 && !allIn){
				if(lastPlayerRaising == null)lastPlayerRaising = p;
				informAllPlayers(new StringMessage("check"), p.getActorRef());
			}
			// bet & raise & call:
			else if(playersBet >= bet && !allIn){
				if (playersBet > bet) {
					lastPlayerRaising = p;
					informAllPlayers(new StringMessage("raise to " + Integer.toString(playersBet)), p.getActorRef());
					informAllPlayers(new InformActualBet(playersBet), getSelf());
				}
				else
				{
					informAllPlayers(new StringMessage("call"), p.getActorRef());
				}
				bet = playersBet;
				
				p.takeBet(playersBet);
				tableMoney += playersBet;
			}
			//fold
			else {
				informAllPlayers(new StringMessage("fold"), p.getActorRef());
				p.setStatus(Player.Status.Inactive);	
			}
			
			if(!iterP.hasNext())
			{
				iterP = players.iterator();
			}
			p = iterP.next();
		}
		
		
	}
	
	/**
	 * Charges players with entrance fee
	 */
	private void entranceBet()
	{
		for(Player p: players)
		{
			if(p.getCoins() < entranceBet)
			{
				removePlayer(p);
				continue;
			}
			
			if(p.getStatus() == Player.Status.Active)
			{
				p.prepareForNewBetting();
				p.takeBet(entranceBet);
				tableMoney += entranceBet;
			}
		}
	}
	
	/**
	 * Starts a game with actual players
	 * @throws Exception 
	 */
	private void playGame() throws Exception
	{
		AllowJoining = false;
		
		while(activeP > 1)
		{
			playParty();
			
			Player p = players.get(0);
			players.remove(0);
			players.add(p);
		}
		
		for(Player p: players)
		{
			informAllPlayers(new StringMessage(p.name + "( coins: " + p.getCoins() + "; number of wins: " + p.getNoWinning()), getSelf());
		}
	}
	
	/**
	 * Starts a party with a actual players
	 * @throws Exception 
	 */
	
	private void playParty() throws Exception {
		deck.shuffle();
		tableMoney = 0;
		allIn = false;
		
		for(Player p: players)
		{
			if(p.getStatus() != Player.Status.Archived)p.setStatus(Player.Status.Active);
		}
		
		entranceBet();
		
		for(Player p: players)
		{
			if(p.getStatus() == Player.Status.Active)
			{
				p.fillHand();
			}
		}
		
		betting();
		
		for(Player p: players)
		{
			if(p.getStatus() == Player.Status.Active)
			{
				p.exchangeCards();
			}
		}
		
		List<Player> leftPlayers = new LinkedList<Player>();
		
		for(Player p: players)
		{
			if(p.getStatus() == Player.Status.Active)
			{
				leftPlayers.add(p);
				informAllPlayers("my cards - " + p.getCards().toString(), p.getActorRef());
			}
		}
		
		Collections.sort(leftPlayers, new ListComparator());
		Collections.reverse(leftPlayers);
		
		ListComparator comp = new ListComparator();
		
		Iterator<Player> iter = leftPlayers.iterator();
		
		Player lastPwv = leftPlayers.get(0);
		while(iter.hasNext()) {
			Player pwv = iter.next();
			if(comp.compare(pwv, lastPwv) != 0) {
				lastPwv = pwv;
				break;
			}
		}
		int winners = 0;
		System.out.println("Winning players: ");
		iter = leftPlayers.iterator();
		
		while(iter.hasNext())
		{
			Player pwv = iter.next();
			if(pwv == lastPwv)break;
			System.out.println(pwv.getName() + ", ");
			/*** Set number of winnings */
			winners++;
		}
		
		if(winners == 1){
			//zabrac rownowartosc kasy winnera od innych playerow
			if(allIn){
				int winnersBet = leftPlayers.get(0).getCoins();
				for(Player p: leftPlayers)
				{
					if(p.equals(leftPlayers.get(0)) || p.getStatus() != Player.Status.Active)continue;
					int loosersBet = p.getCoins();
					
					leftPlayers.get(0).giveCoins(Math.min(loosersBet, winnersBet));
					p.takeBet(Math.min(loosersBet, winnersBet));
				}
			}
			leftPlayers.get(0).giveCoins(tableMoney);
			tableMoney = 0;
		}
		else
		{
			tableMoney = 0;
			informAllPlayers(new StringMessage("There was no winners"), getSelf());
		}
	}
    
    
}

